Plugin Directory: server-render screenshots gallery + Gallery Lightbox Enhancements polyfill#622
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @thilinah. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
A bit of context on how we got here: we first proposed an upstream Gutenberg fix for lightbox captions and, separately, a masonry block style for On captions specifically: every figure ships its caption text, but it surfaces only inside the lightbox overlay and never on the gallery itself. Plugin authors routinely write multi-sentence captions, and rendering those under each thumbnail would tear the brick layout apart with uneven figure heights. The lightbox is the right surface for that copy - it has room for it without compromising the grid. |
There was a problem hiding this comment.
Pull request overview
Replaces the wporg-plugins-2024 theme’s bespoke React screenshot gallery with a server-rendered [wporg-plugins-screenshots] shortcode in the Plugin Directory plugin, and introduces a small “Gallery Lightbox Enhancements” polyfill plugin to fill Gutenberg lightbox caption + gallery masonry gaps during the wp.org migration.
Changes:
- Added a new server-rendered screenshots shortcode that outputs
core/gallery+core/imageblocks, includes reveal behavior for large galleries, and adds Photon preconnect + responsivesrcsethandling. - Added a new polyfill plugin that registers an
is-style-masonryGallery style and restores lightbox caption rendering. - Removed the legacy theme JS + SCSS screenshot/gallery implementation and updated built CSS artifacts accordingly.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/package.json | Removes legacy JS build script for the old screenshot UI. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/js/build/theme.js | Deletes built legacy React screenshot bundle. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/js/build/theme.asset.php | Deletes asset metadata for the removed legacy bundle. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/functions.php | Stops enqueuing the legacy React bundle on single plugin pages. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/css/style.css | Regenerated theme CSS after removing screenshot/gallery SCSS. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/css/style-rtl.css | Regenerated RTL theme CSS after removing screenshot/gallery SCSS. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/theme.js | Removes legacy screenshot initialization entrypoint. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/styles/components/_components.scss | Stops importing the old screenshot/gallery component styles. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/screenshots/index.js | Removes old Screenshots component. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/screenshots/image-gallery.js | Removes old ImageGallery implementation. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/components/plugin/_sections-screenshots.scss | Removes legacy screenshots section styles. |
| wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/client/components/plugin/_sections-image-gallery.scss | Removes legacy image gallery styles. |
| wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php | New SSR shortcode: block markup generation, reveal wrapper, Photon hints, and dimension probing. |
| wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/assets/screenshots.js | Adds client-side reveal behavior + broken-image handling for large galleries. |
| wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/assets/screenshots.css | Adds shortcode-scoped masonry/grid styling and reveal UI styles. |
| wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php | Emits Photon preconnect hints on plugin singular pages. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/readme.txt | New plugin readme describing the Gutenberg polyfills. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/LICENSE | Adds GPL license file for the new plugin. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/includes/class-version-guard.php | Adds a guard to self-disable the polyfill when core ships the features. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/includes/class-masonry-style.php | Registers/enqueues a masonry style variation for core/gallery. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/includes/class-lightbox-captions.php | Adds caption text to interactivity state and enqueues runtime assets. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/gallery-lightbox-enhancements.php | Plugin bootstrap: loads guarded features. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/masonry.css | CSS for the is-style-masonry Gallery variation. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.js | Runtime augmentation to render captions inside the lightbox overlay. |
| wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/assets/lightbox-captions.css | Caption + scrim styling for the lightbox overlay. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| while ( $offset + 8 < $length ) { | ||
| if ( "\xFF" !== $bytes[ $offset ] ) { | ||
| return false; | ||
| } | ||
| $marker = ord( $bytes[ $offset + 1 ] ); | ||
| ++$offset; | ||
| // Skip 0xFF padding bytes between segments. | ||
| while ( 0xFF === $marker && $offset < $length ) { | ||
| $marker = ord( $bytes[ $offset ] ); | ||
| ++$offset; | ||
| } | ||
| $is_sof = ( | ||
| ( $marker >= 0xC0 && $marker <= 0xC3 ) || | ||
| ( $marker >= 0xC5 && $marker <= 0xC7 ) || | ||
| ( $marker >= 0xC9 && $marker <= 0xCB ) || | ||
| ( $marker >= 0xCD && $marker <= 0xCF ) | ||
| ); | ||
| if ( $is_sof && $offset + 7 < $length ) { | ||
| $dim = unpack( 'nheight/nwidth', substr( $bytes, $offset + 3, 4 ) ); | ||
| if ( $dim && $dim['width'] > 0 && $dim['height'] > 0 ) { | ||
| return array( $dim['width'], $dim['height'] ); | ||
| } | ||
| return false; | ||
| } | ||
| if ( $offset + 1 >= $length ) { | ||
| return false; | ||
| } | ||
| $segment_length = unpack( 'nlen', substr( $bytes, $offset, 2 ) ); | ||
| if ( ! $segment_length || $segment_length['len'] < 2 ) { | ||
| return false; | ||
| } |
| * - On init, measure the natural bottom of the ninth figure across | ||
| * the columns and write it into `--collapse-height` so the wrap | ||
| * clips at exactly the right line. | ||
| * - On click, measure the wrap's full `scrollHeight` and write it | ||
| * into `--full-height`, then flip the `is-revealed` class so the | ||
| * CSS transition animates from collapse-height to full-height. |
There was a problem hiding this comment.
Done in 814a32d - removed the unused COMPACT_VISIBLE constant, documented the $dimensions param on build_gallery_markup(), and refreshed the CSS/JS/class-header comments that still described the dropped dynamic --collapse-height measurement.
| * `--collapse-height` is set inline by the JS on init (it measures the | ||
| * natural bottom edge of the ninth figure across all three columns). | ||
| * If JS hasn't run yet, the fallback value of 36rem covers the typical | ||
| * "three rows of 16:10 landscape thumbs" footprint. |
| /** | ||
| * Builds the block markup string for the Gallery + Image blocks. | ||
| * | ||
| * Every screenshot renders into the markup — collapse / expand is | ||
| * a CSS `max-height` clip on the gallery wrap, not a DOM hide. This | ||
| * keeps the masonry balanced across columns once and prevents the | ||
| * "figures reshuffle" effect the user saw on click previously. | ||
| * | ||
| * @param array $screenshots Screenshots returned by Template::get_screenshots(). | ||
| * @param int $count Total screenshot count, used to size columns. | ||
| * @return string Block markup ready for do_blocks(). | ||
| */ | ||
| protected static function build_gallery_markup( $screenshots, $count, $dimensions = array() ) { | ||
| $inner = ''; |
2dcc0b6 to
ebb5f7a
Compare
Replaced the wporg-plugins-2024 theme JS gallery with a server-rendered [wporg-plugins-screenshots] shortcode in the Plugin Directory plugin, and added the Gallery Lightbox Enhancements polyfill that ports two pending Gutenberg pull requests (lightbox captions #77477 and the masonry block style #77615). The screenshot UX is now server-side, cacheable by Varnish, works with JS disabled, and renders through core/gallery + core/image blocks instead of bespoke theme JS. The shortcode renders every figure upfront with eager loading, intrinsic width and height attributes, and fetchpriority="high" on the cap-window thumbnails so there is zero layout shift while screenshots decode. Galleries up to nine figures show the full grid; larger ones render a "Show all N screenshots" reveal button that animates max-height on the wrap to full size with a setTimeout fallback so the cap always releases. Layout is deterministic: N=1 single tile, N=2-4 row-aligned grid, N>=5 brick masonry by default with a row-aligned grid upgrade when every screenshot shares an aspect ratio. Aspect ratios and pixel dimensions come from a single Memcached-cached probe over the screenshot URLs (md5-keyed so a revision bump auto-invalidates). Lightbox captions and a generic Masonry block style ship through the polyfill plugin so they self-disable once core lands the upstream PRs. Removed the legacy theme implementation: client/screenshots/, the dedicated theme.js entry, the build:old:js npm script, and the matching SCSS partials. The theme no longer carries any screenshot-specific JavaScript or stylesheets - the shortcode owns the entire flow. Affected: wordpress.org/public_html/wp-content/plugins/gallery-lightbox-enhancements/, wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/, wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php, wordpress.org/public_html/wp-content/themes/pub/wporg-plugins-2024/
ebb5f7a to
9d889d8
Compare
There was a problem hiding this comment.
@dan-zakirov Thanks for the PR! I've added some commentary below, I've only code reviewed, not actually tried running it, but it looks like a strong start!
I'm going to fire up a PR to extract the resolution of assets at import time, and backfill them, to make the code cleaner and allow such improvements to also be used for the WordPress.org plugins API.
I haven't checked, but I think I recall the output of this is used in the WordPress.org plugins api somewhere, somehow..
Switched the screenshot count placeholder from %d to %s and ran $count through number_format_i18n() so locales with non-Latin digit grouping render the value correctly. Keeps the same _n() pluralisation and the same translator comment, with %s in both. Addresses dd32's review comment on lines 423-425 (PR WordPress#622).
| * @return string Image block markup. | ||
| */ | ||
| protected static function build_image_block( $screenshot, $id, $above_fold = false, $dimensions = null ) { | ||
| $src = isset( $screenshot['src'] ) ? esc_url( $screenshot['src'] ) : ''; |
| ) | ||
| ); | ||
|
|
||
| $srcset = self::photon_srcset( $src ); |
There was a problem hiding this comment.
@dan-zakirov I think is a small change worth addressing.
| // and `height` attributes — the browser reserves slot height | ||
| // from the intrinsic aspect ratio, so the gallery has zero | ||
| // layout shift no matter how slowly the screenshots decode. | ||
| $dimensions = self::get_dimensions( $screenshots ); |
There was a problem hiding this comment.
@dan-zakirov I think it would be better to address this too.
| if ( self::is_uniform_aspect( $screenshots ) ) { | ||
| $layout_class .= ' has-uniform-aspect'; | ||
| } |
| return true; | ||
| } | ||
|
|
||
| $dimensions = self::get_dimensions( $screenshots ); |
Replaced the hand-rolled emit_preconnect() that printed <link> tags straight into wp_head with a wp_resource_hints filter callback. The filter is the native WordPress integration point for preconnect and dns-prefetch hints, lets other code dedupe and reorder, and reads more naturally next to the rest of the plugin-directory hooks. Hint now runs on every singular plugin page (production or local) — the previous production-only guard with function_exists fallback was dropped: the hint is harmless in any environment and we target modern WP without backcompat shims. Addresses dd32's review comment on lines 192-193 (PR WordPress#622).
Moved screenshots.css and screenshots.js from shortcodes/assets/ to the plugin's existing css/ and js/ directories, alongside edit-form.css and edit-form.js. The shortcode no longer creates a one-off asset folder under itself — versioning through filemtime() is now inlined directly in plugins_url() / filemtime() calls without the file_exists() fallback and the helper variables. Addresses dd32's review comments on lines 208-230 (PR WordPress#622).
| /** | ||
| * Visible figure count when collapse is active. | ||
| * | ||
| * Eight figures across three columns settles into a 3+3+2 brick — the | ||
| * partial bottom row reads as "more below" under the fade overlay. | ||
| * | ||
| * @var int | ||
| */ | ||
| const COMPACT_VISIBLE = 8; | ||
|
|
| * Reveal wrap. Clips the gallery via `max-height` while collapsed; the | ||
| * fade overlay and the Show-all button are absolutely positioned over | ||
| * the clipped area so their position adapts to the column heights — | ||
| * no dead band beneath the shortest column. | ||
| * | ||
| * `--collapse-height` is set inline by the JS on init (it measures the | ||
| * natural bottom edge of the ninth figure across all three columns). | ||
| * If JS hasn't run yet, the fallback value of 36rem covers the typical | ||
| * "three rows of 16:10 landscape thumbs" footprint. |
str_contains() requires PHP 8.0+, but the plugin header declares Requires PHP: 7.4. Switched the lightbox-container detection check to strpos() so the plugin loads cleanly on the minimum supported PHP version instead of fatal-erroring on first hook fire.
Previously the guard only watched GUTENBERG_VERSION, so on vanilla WordPress installs that never load the Gutenberg plugin the polyfill ran forever even after the upstream features landed in core. Added WP_VERSION_WITH_FEATURES alongside the Gutenberg constant: should_load() now steps aside when either core or the Gutenberg plugin ships the features, and falls back to "load" only while both constants are still sentinels. This covers the two real-world paths: wordpress.org runs trunk + the latest Gutenberg release (so the Gutenberg check fires first), and plain sites pick up the features through core (so the WP check fires).
Three documentation-only cleanups in the screenshots shortcode: - Removed the COMPACT_VISIBLE constant. It was a leftover from an earlier "render only N tiles in the collapsed state" design; the current implementation renders every figure into the DOM and clips the wrap with CSS max-height, so the constant has no callers. - Updated the class header docblock to match: it now describes the fixed max-height collapse cap instead of the removed per-tile cap. - Updated the screenshots.css and js/screenshots.js header comments the same way: they used to describe a JS measurement that wrote --collapse-height inline. The measurement was already gone, only the comments lagged behind. - Documented the new $dimensions parameter on build_gallery_markup().
|
|
||
| wp_enqueue_script( | ||
| 'wporg-plugins-screenshots', | ||
| plugins_url( 'js/screenshots.js', __DIR__ . '/../plugin-directory.php' ), | ||
| array(), | ||
| (string) filemtime( __DIR__ . '/../js/screenshots.js' ), | ||
| true | ||
| ); |
| * The collapse height is a fixed `max-height: 32rem` cap on the wrap | ||
| * (about three rows of typical landscape thumbs). We don't measure | ||
| * dynamically: lazy figures past the fold report `naturalWidth: 0` | ||
| * until they decode, which would make the measured height equal to | ||
| * the unclipped natural height and defeat the collapse entirely. |
- Removed fetch_dimensions() and parse_image_dimensions(); width and height now come from the assets_screenshots post meta, populated by Import::enrich_asset_dimensions() in r14866 and inlined onto every screenshot entry by Template::get_screenshots(). - Simplified get_dimensions() to a plain pluck over the screenshot list; dropped the CACHE_GROUP constant and the Memcached verdict cache, both unnecessary now that the lookup is in-process. - Kept the is_uniform_aspect() partial-fail fallback intact, so a missing dimension pair still degrades to brick masonry without breaking the gallery. Affected: wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php
|
Hi! Thanks for your work here. Since @dd32 already reviewed the code earlier, I didn't look at the code (yet), but I did a first round of testing. Things look pretty good! Only found one small thing: when opening the page with javascript disabled, you can click on an image but nothing happens. Plan to do another round of extra testing. |
Thanks for the round and screenshots. The no-JS click is inherited from core/image's lightbox attribute, same applies to any lightbox-enabled core image, so it's not a PR regression. If you want, I can wrap the img in This PR is a first cut focused on retiring the old gallery quickly. Edge cases by screenshot count and height will land in follow-ups. |
This sounds like a good UX improvement to me! Especially that it's low effort, I'd propose to go with it :) |
- Wrapped each <img> in build_image_block() with an <a href="{src}" target="_blank" rel="noopener"> so the no-JS reader still gets a path to the full-resolution screenshot in a new tab.
- Core's lightbox interactivity script attaches its own click handler on hydration and calls preventDefault() before the anchor would navigate, so JS readers keep the existing lightbox flow unchanged.
Affected: wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php
Done in 7f29154. Wrapped each img in |
| return $content; | ||
| } | ||
|
|
||
| wp_interactivity_state( |
There was a problem hiding this comment.
The light box is readign the caption from image alt attribute. So do we need this?
|
I've trsted this PR on a sandbox and it mostly work as expected. Add a few comments on some Copilot suggestions. But when I click on an image it opens a new tab with a full size image. Then I have to go back to the previous tab to see the light box. Screen.Recording.2026-06-05.at.15.30.07.movI think we should stop opening the full size image since we show the light box. |










Replaces the bespoke
wporg-plugins-2024theme JS screenshots gallery with a server-rendered[wporg-plugins-screenshots]shortcode in the Plugin Directory plugin, and adds a small polyfill plugin (Gallery Lightbox Enhancements) that restores two long-standing Gutenberg gaps locally so the wp.org migration can ship without waiting on core.Visual evidence
Layout cases — counts 1 to 9
Reveal flow (N≥10)
Mobile (≤599 px)
What changes
wp-content/plugins/plugin-directory/shortcodes/class-screenshots.php— new shortcode that renders every screenshot throughcore/gallery+core/imageblocks, lightbox enabled per tile. Galleries up to 9 figures show the full grid; larger ones get aShow all N screenshotsreveal button. Layout is deterministic: N=1 single tile, N=2-4 row-aligned grid, N≥5 brick masonry by default with a row-aligned grid upgrade when every screenshot shares an aspect ratio.wp-content/plugins/gallery-lightbox-enhancements/— polyfill plugin that adds ais-style-masonryblock style forcore/galleryand rendersfigcaptiontext inside the lightbox overlay. Both behaviours are gated behindVersion_Guard::should_load(), which lets the plugin self-disable once core ships the features.wp-content/themes/pub/wporg-plugins-2024/— removes the legacy theme implementation:client/screenshots/, the dedicatedtheme.jsentry, the matching SCSS partials, and thebuild:old:jsnpm script.Why
Server-side rendering makes the gallery cacheable by Varnish, works without JS, and gives third-party scrapers and SEO crawlers the full markup. Every
<img>ships withloading="eager", intrinsicwidth/heightattributes, andfetchpriority="high"on the cap-window thumbnails, so CLS stays at 0 while screenshots decode. Aspect ratios and pixel dimensions come from a single Memcached-cached probe (md5-keyed by URL list, so a screenshot revision auto-invalidates).Closes / refs
Meta Trac (the long-standing screenshot UX gaps this PR addresses):
fetchpriority, see "Why" above)Upstream Gutenberg gaps that the polyfill plugin addresses on the user side:
Prior art on this task
Earlier attempts at the same migration that fed into this PR's design:
Safety / scope
The change is additive on the Plugin Directory side and isolated for the polyfill, with one purposeful subtraction in the theme. Map of every touched path:
wp-content/plugins/gallery-lightbox-enhancements/(new plugin, 9 files)Version_Guard::should_load()once core ships the features. Activation lives outside this PR (mu-plugin loader on production).wp-content/plugins/plugin-directory/shortcodes/class-screenshots.phpdo_shortcode( '[wporg-plugins-screenshots]' )keeps working.wp-content/plugins/plugin-directory/shortcodes/assets/screenshots.css,screenshots.jswp-content/plugins/plugin-directory/class-plugin-directory.php(+1)add_filter( 'wp_resource_hints', [ Screenshots::class, 'add_resource_hints' ] )that contributespreconnect/dns-prefetchURLs for the Photon host on single-plugin pages. Hint only, no blocking behaviour.wp-content/themes/pub/wporg-plugins-2024/client/screenshots/(deleted)wp-content/themes/pub/wporg-plugins-2024/css/style.css,style-rtl.cssbuild:old:jsscript. No theme JS or CSS for screenshots remains — the shortcode owns the whole flow.The migration does not touch DB schema, post types, taxonomies, REST endpoints, or any sibling shortcode in
plugin-directory/shortcodes/.Notes for reviewers
phpcs.xml.distruleset (WordPress-Core / Docs / Extra, PHP 8.4 compat).Version_Guardplaceholder strategy and on the deletion of the oldclient/screenshots/modules.